﻿using HtmlAgilityPack;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Net;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections.Generic;

namespace WebCrawler
{
    class Program
    {
        // 定义一个队列存储待爬取的URL
        static Queue<string> urlQueue = new Queue<string>();
        // 定义一个哈希集合存储已爬取的URL
        static HashSet<string> urlCrawled = new HashSet<string>();
        // 定义一个哈希表存储电话号码和对应的URL列表
        static Dictionary<string, List<string>> phoneNumbers = new Dictionary<string, List<string>>();
        // 定义一个正则表达式匹配高校电话号码
        static Regex phoneRegex = new Regex(@"\d{2,4}-\d{7,8}");
        // 定义一个最大爬取数量
        static int maxCount = 100;

        static async Task Main(string[] args)
        {
            // 获取用户输入的关键字
            Console.WriteLine("请输入搜索关键字：");
            string keyword = Console.ReadLine();
            // 使用baidu搜索引擎搜索关键字，并将结果页面加入队列
            string baiduUrl = "https://www.baidu.com/s?wd=" + keyword;
            urlQueue.Enqueue(baiduUrl);
            // 使用bing搜索引擎搜索关键字，并将结果页面加入队列
            string bingUrl = "https://www.bing.com/search?q=" + keyword;
            urlQueue.Enqueue(bingUrl);
            // 开始爬取网页，直到队列为空或者达到最大数量
            while (urlQueue.Count > 0 && phoneNumbers.Count < maxCount)
            {
                // 从队列中取出一个URL，并将其加入已爬取集合
                string url = urlQueue.Dequeue();
                urlCrawled.Add(url);
                // 显示已爬取的URL
                Console.WriteLine("已爬取：" + url);
                // 获取网页内容
                string html = await GetHtmlAsync(url);
                if (html != null)
                {
                    // 解析网页内容，获取电话号码和链接
                    ParseHtml(html, url);
                }
            }
            // 显示爬取到的电话号码和对应的URL列表
            Console.WriteLine("爬取到的电话号码如下：");
            foreach (var pair in phoneNumbers)
            {
                Console.WriteLine(pair.Key + ":");
                foreach (var u in pair.Value)
                {
                    Console.WriteLine("\t" + u);
                }
            }
            // 将电话号码和对应的URL列表保存到CSV文件中
            SaveToCsv("phone_numbers.csv");
        }

        // 定义一个异步方法获取网页内容
        static async Task<string> GetHtmlAsync(string url)
        {
            try
            {
                // 创建一个HttpClient对象，设置请求头和超时时间
                HttpClient client = new HttpClient();
                client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36");
                client.Timeout = TimeSpan.FromSeconds(10);
                // 发送GET请求，获取响应内容
                HttpResponseMessage response = await client.GetAsync(url);
                response.EnsureSuccessStatusCode();
                string html = await response.Content.ReadAsStringAsync();
                return html;
            }
            catch (Exception e)
            {
                // 如果发生异常，返回null
                Console.WriteLine(e.Message);
                return null;
            }
        }

        // 定义一个方法解析网页内容，获取电话号码和链接
        static void ParseHtml(string html, string url)
        {
            // 创建一个HtmlDocument对象，加载网页内容
            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(html);
            // 获取网页中的所有链接标签
            var links = doc.DocumentNode.SelectNodes("//a[@href]");
            if (links != null)
            {
                // 遍历每个链接标签，获取其href属性值
                foreach (var link in links)
                {
                    string href = link.GetAttributeValue("href", "");
                    // 如果href属性值不为空，且不是javascript或mailto开头的
                    if (!string.IsNullOrEmpty(href) && !href.StartsWith("javascript:") && !href.StartsWith("mailto:"))
                    {
                        // 如果href属性值是相对路径，转换为绝对路径
                        if (!href.StartsWith("http"))
                        {
                            href = new Uri(new Uri(url), href).AbsoluteUri;
                        }
                        // 如果href属性值不在已爬取集合或队列中，将其加入队列
                        if (!urlCrawled.Contains(href) && !urlQueue.Contains(href))
                        {
                            urlQueue.Enqueue(href);
                        }
                    }
                }
            }
            // 获取网页中的所有文本内容
            var texts = doc.DocumentNode.SelectNodes("//text()");
            if (texts != null)
            {
                // 遍历每个文本内容，使用正则表达式匹配电话号码
                foreach (var text in texts)
                {
                    string content = text.InnerText.Trim();
                    MatchCollection matches = phoneRegex.Matches(content);
                    // 遍历每个匹配结果，如果是有效的电话号码，将其加入哈希表中，并记录对应的URL
                    foreach (Match match in matches)
                    {
                        string phoneNumber = match.Value;
                        if (IsValidPhoneNumber(phoneNumber))
                        {
                            if (!phoneNumbers.ContainsKey(phoneNumber))
                            {
                                phoneNumbers[phoneNumber] = new List<string>();
                            }
                            phoneNumbers[phoneNumber].Add(url);
                            // 如果已经达到最大数量，跳出循环
                            if (phoneNumbers.Count >= maxCount)
                            {
                                break;
                            }
                        }
                    }
                }
            }
        }

        // 定义一个方法判断电话号码是否有效
        static bool IsValidPhoneNumber(string phoneNumber)
        {
            // 简单地判断电话号码是否以0开头
            return phoneNumber.StartsWith("0");
        }

        // 定义一个方法将电话号码和对应的URL列表保存到CSV文件中
        static void SaveToCsv(string fileName)
        {
            // 创建一个文件流对象，以追加模式打开或创建文件
            FileStream fs = new FileStream(fileName, FileMode.Append, FileAccess.Write);
            // 创建一个StreamWriter对象，使用UTF-8编码写入文件
            StreamWriter sw = new StreamWriter(fs, Encoding.UTF8);
            // 遍历每个电话号码和对应的URL列表，写入一行数据
            foreach (var pair in phoneNumbers)
            {
                string phoneNumber = pair.Key;
                string urls = string.Join(";", pair.Value);
                sw.WriteLine(phoneNumber + "," + urls);
            }
            // 关闭文件流对象和StreamWriter对象
            sw.Close();
            fs.Close();
        }
    }
}
